(function() {
	var TRANSITION_FALLBACK_DURATION = 500;
	var hideElement = function(el) {

		if (!el) {
			return;
		}

		var removeThis = function() {
			if (el && el.parentNode) {
				el.parentNode.removeChild(el);
			}
		};

		el.classList.remove("show");
		el.classList.add("hide");
		el.addEventListener("transitionend", removeThis);

		// Fallback for no transitions.
		setTimeout(removeThis, TRANSITION_FALLBACK_DURATION);

	};

	function Alertify() {

		/**
		 * Alertify private object
		 * @type {Object}
		 */
		var _alertify = {
			parent: document.body,
			version: "1.0.10",
			defaultOkLabel: "确定",
			okLabel: "确定",
			defaultCancelLabel: "取消",
			cancelLabel: "取消",
			defaultMaxLogItems: 2,
			maxLogItems: 2,
			promptValue: "",
			promptPlaceholder: "",
			closeLogOnClick: false,
			closeLogOnClickDefault: false,
			delay: 5000,
			defaultDelay: 5000,
			logContainerClass: "alertify-logs",
			logContainerDefaultClass: "alertify-logs",
			dialogs: {
				buttons: {
					holder: "<nav>{{buttons}}</nav>",
					ok: "<button class='ok' tabindex='1'>{{ok}}</button>",
					cancel: "<button class='cancel' tabindex='2'>{{cancel}}</button>"
				},
				input: "<input type='text'>",
				message: "<p class='msg'>{{message}}</p>",
				log: "<div class='{{class}}'>{{message}}</div>"
			},

			defaultDialogs: {
				buttons: {
					holder: "<nav>{{buttons}}</nav>",
					ok: "<button class='ok' tabindex='1'>{{ok}}</button>",
					cancel: "<button class='cancel' tabindex='2'>{{cancel}}</button>"
				},
				input: "<input type='text'>",
				message: "<p class='msg'>{{message}}</p>",
				log: "<div class='{{class}}'>{{message}}</div>"
			},

			/**
			 * Build the proper message box
			 *
			 * @param  {Object} item    Current object in the queue
			 *
			 * @return {String}         An HTML string of the message box
			 */
			build: function(item) {

				var btnTxt = this.dialogs.buttons.ok;
				var html = "<div class='dialog'>" + "<div>" + this.dialogs.message.replace("{{message}}", item.message);

				if (item.type === "confirm" || item.type === "prompt") {
					btnTxt = this.dialogs.buttons.cancel + this.dialogs.buttons.ok;
				}

				if (item.type === "prompt") {
					html += this.dialogs.input;
				}

				html = (html + this.dialogs.buttons.holder + "</div>" + "</div>")
					.replace("{{buttons}}", btnTxt)
					.replace("{{ok}}", this.okLabel)
					.replace("{{cancel}}", this.cancelLabel);

				return html;

			},

			setCloseLogOnClick: function(bool) {
				this.closeLogOnClick = !!bool;
			},

			/**
			 * Close the log messages
			 *
			 * @param  {Object} elem    HTML Element of log message to close
			 * @param  {Number} wait    [optional] Time (in ms) to wait before automatically hiding the message, if 0 never hide
			 *
			 * @return {undefined}
			 */
			close: function(elem, wait) {

				if (this.closeLogOnClick) {
					elem.addEventListener("click", function(ev) {
						hideElement(ev.srcElement);
					});
				}

				wait = wait && !isNaN(+wait) ? +wait : this.delay;

				if (wait < 0) {
					hideElement(elem);
				} else if (wait > 0) {
					setTimeout(function() {
						hideElement(elem);
					}, wait);
				}

			},

			/**
			 * Create a dialog box
			 *
			 * @param  {String}   message      The message passed from the callee
			 * @param  {String}   type         Type of dialog to create
			 * @param  {Function} onOkay       [Optional] Callback function when clicked okay.
			 * @param  {Function} onCancel     [Optional] Callback function when cancelled.
			 *
			 * @return {Object}
			 */
			dialog: function(message, type, onOkay, onCancel) {
				return this.setup({
					type: type,
					message: message,
					onOkay: onOkay,
					onCancel: onCancel
				});
			},

			/**
			 * Show a new log message box
			 *
			 * @param  {String} message    The message passed from the callee
			 * @param  {String} type       [Optional] Optional type of log message
			 * @param  {Number} wait       [Optional] Time (in ms) to wait before auto-hiding the log
			 *
			 * @return {Object}
			 */
			log: function(message, type, click) {

				var existing = document.querySelectorAll(".alertify-logs > div");
				if (existing) {
					var diff = existing.length - this.maxLogItems;
					if (diff >= 0) {
						for (var i = 0, _i = diff + 1; i < _i; i++) {
							this.close(existing[i], -1);
						}
					}
				}

				this.notify(message, type, click);
			},

			setLogPosition: function(str) {
				this.logContainerClass = "alertify-logs " + str;
			},

			setupLogContainer: function() {

				var elLog = document.querySelector(".alertify-logs");
				var className = this.logContainerClass;
				if (!elLog) {
					elLog = document.createElement("div");
					elLog.className = className;
					this.parent.appendChild(elLog);
				}

				// Make sure it's positioned properly.
				if (elLog.className !== className) {
					elLog.className = className;
				}

				return elLog;

			},

			/**
			 * Add new log message
			 * If a type is passed, a class name "{type}" will get added.
			 * This allows for custom look and feel for various types of notifications.
			 *
			 * @param  {String} message    The message passed from the callee
			 * @param  {String} type       [Optional] Type of log message
			 * @param  {Number} wait       [Optional] Time (in ms) to wait before auto-hiding
			 *
			 * @return {undefined}
			 */
			notify: function(message, type, click) {

				var elLog = this.setupLogContainer();
				var log = document.createElement("div");

				log.className = (type || "default");
				if (_alertify.logTemplateMethod) {
					log.innerHTML = _alertify.logTemplateMethod(message);
				} else {
					log.innerHTML = message;
				}

				// Add the click handler, if specified.
				if ("function" === typeof click) {
					log.addEventListener("click", click);
				}

				elLog.appendChild(log);
				setTimeout(function() {
					log.className += " show";
				}, 10);

				this.close(log, this.delay);

			},

			/**
			 * Initiate all the required pieces for the dialog box
			 *
			 * @return {undefined}
			 */
			setup: function(item) {

				var el = document.createElement("div");
				el.className = "alertify hide";
				el.innerHTML = this.build(item);

				var btnOK = el.querySelector(".ok");
				var btnCancel = el.querySelector(".cancel");
				var input = el.querySelector("input");
				var label = el.querySelector("label");

				// Set default value/placeholder of input
				if (input) {
					if (typeof this.promptPlaceholder === "string") {
						// Set the label, if available, for MDL, etc.
						if (label) {
							label.textContent = this.promptPlaceholder;
						} else {
							input.placeholder = this.promptPlaceholder;
						}
					}
					if (typeof this.promptValue === "string") {
						input.value = this.promptValue;
					}
				}

				function setupHandlers(resolve) {
					if ("function" !== typeof resolve) {
						// promises are not available so resolve is a no-op
						resolve = function() {};
					}

					if (btnOK) {
						btnOK.addEventListener("click", function(ev) {
							if (item.onOkay && "function" === typeof item.onOkay) {
								if (input) {
									item.onOkay(input.value, ev);
								} else {
									item.onOkay(ev);
								}
							}

							if (input) {
								resolve({
									buttonClicked: "ok",
									inputValue: input.value,
									event: ev
								});
							} else {
								resolve({
									buttonClicked: "ok",
									event: ev
								});
							}

							hideElement(el);
						});
					}

					if (btnCancel) {
						btnCancel.addEventListener("click", function(ev) {
							if (item.onCancel && "function" === typeof item.onCancel) {
								item.onCancel(ev);
							}

							resolve({
								buttonClicked: "cancel",
								event: ev
							});

							hideElement(el);
						});
					}
				}

				var promise;

				if (typeof Promise === "function") {
					promise = new Promise(setupHandlers);
				} else {
					setupHandlers();
				}

				this.parent.appendChild(el);
				setTimeout(function() {
					el.classList.remove("hide");
					if (input && item.type && item.type === "prompt") {
						input.select();
						input.focus();
					} else {
						if (btnOK) {
							btnOK.focus();
						}
					}
				}, 100);

				return promise;
			},

			okBtn: function(label) {
				this.okLabel = label;
				return this;
			},

			setDelay: function(time) {
				time = time || 0;
				this.delay = isNaN(time) ? this.defaultDelay : parseInt(time, 10);
				return this;
			},

			cancelBtn: function(str) {
				this.cancelLabel = str;
				return this;
			},

			setMaxLogItems: function(num) {
				this.maxLogItems = parseInt(num || this.defaultMaxLogItems);
			},

			theme: function(themeStr) {
				switch (themeStr.toLowerCase()) {
					case "bootstrap":
						this.dialogs.buttons.ok = "<button class='ok btn btn-primary' tabindex='1'>{{ok}}</button>";
						this.dialogs.buttons.cancel = "<button class='cancel btn btn-default' tabindex='2'>{{cancel}}</button>";
						this.dialogs.input = "<input type='text' class='form-control'>";
						break;
					case "purecss":
						this.dialogs.buttons.ok = "<button class='ok pure-button' tabindex='1'>{{ok}}</button>";
						this.dialogs.buttons.cancel = "<button class='cancel pure-button' tabindex='2'>{{cancel}}</button>";
						break;
					case "mdl":
					case "material-design-light":
						this.dialogs.buttons.ok = "<button class='ok mdl-button mdl-js-button mdl-js-ripple-effect'  tabindex='1'>{{ok}}</button>";
						this.dialogs.buttons.cancel = "<button class='cancel mdl-button mdl-js-button mdl-js-ripple-effect' tabindex='2'>{{cancel}}</button>";
						this.dialogs.input = "<div class='mdl-textfield mdl-js-textfield'><input class='mdl-textfield__input'><label class='md-textfield__label'></label></div>";
						break;
					case "angular-material":
						this.dialogs.buttons.ok = "<button class='ok md-primary md-button' tabindex='1'>{{ok}}</button>";
						this.dialogs.buttons.cancel = "<button class='cancel md-button' tabindex='2'>{{cancel}}</button>";
						this.dialogs.input = "<div layout='column'><md-input-container md-no-float><input type='text'></md-input-container></div>";
						break;
					case "default":
					default:
						this.dialogs.buttons.ok = this.defaultDialogs.buttons.ok;
						this.dialogs.buttons.cancel = this.defaultDialogs.buttons.cancel;
						this.dialogs.input = this.defaultDialogs.input;
						break;
				}
			},

			reset: function() {
				this.parent = document.body;
				this.theme("default");
				this.okBtn(this.defaultOkLabel);
				this.cancelBtn(this.defaultCancelLabel);
				this.setMaxLogItems();
				this.promptValue = "";
				this.promptPlaceholder = "";
				this.delay = this.defaultDelay;
				this.setCloseLogOnClick(this.closeLogOnClickDefault);
				this.setLogPosition("bottom left");
				this.logTemplateMethod = null;
			},

			injectCSS: function() {
				if (!document.querySelector("#alertifyCSS")) {
					var head = document.getElementsByTagName("head")[0];
					var css = document.createElement("style");
					css.type = "text/css";
					css.id = "alertifyCSS";
					css.innerHTML = "/* style.css */";
					head.insertBefore(css, head.firstChild);
				}
			},

			removeCSS: function() {
				var css = document.querySelector("#alertifyCSS");
				if (css && css.parentNode) {
					css.parentNode.removeChild(css);
				}
			}

		};

		_alertify.injectCSS();

		return {
			_$$alertify: _alertify,
			parent: function(elem) {
				_alertify.parent = elem;
			},
			reset: function() {
				_alertify.reset();
				return this;
			},
			alert: function(message, onOkay, onCancel) {
				return _alertify.dialog(message, "alert", onOkay, onCancel) || this;
			},
			confirm: function(message, onOkay, onCancel) {
				return _alertify.dialog(message, "confirm", onOkay, onCancel) || this;
			},
			prompt: function(message, onOkay, onCancel) {
				return _alertify.dialog(message, "prompt", onOkay, onCancel) || this;
			},
			log: function(message, click) {
				_alertify.log(message, "default", click);
				return this;
			},
			theme: function(themeStr) {
				_alertify.theme(themeStr);
				return this;
			},
			success: function(message, click) {
				_alertify.log(message, "success", click);
				return this;
			},
			error: function(message, click) {
				_alertify.log(message, "error", click);
				return this;
			},
			cancelBtn: function(label) {
				_alertify.cancelBtn(label);
				return this;
			},
			okBtn: function(label) {
				_alertify.okBtn(label);
				return this;
			},
			delay: function(time) {
				_alertify.setDelay(time);
				return this;
			},
			placeholder: function(str) {
				_alertify.promptPlaceholder = str;
				return this;
			},
			defaultValue: function(str) {
				_alertify.promptValue = str;
				return this;
			},
			maxLogItems: function(num) {
				_alertify.setMaxLogItems(num);
				return this;
			},
			closeLogOnClick: function(bool) {
				_alertify.setCloseLogOnClick(!!bool);
				return this;
			},
			logPosition: function(str) {
				_alertify.setLogPosition(str || "");
				return this;
			},
			setLogTemplate: function(templateMethod) {
				_alertify.logTemplateMethod = templateMethod;
				return this;
			},
			clearLogs: function() {
				_alertify.setupLogContainer().innerHTML = "";
				return this;
			},
			version: _alertify.version
		};
	}

	// AMD, window, and NPM support
	if ("undefined" !== typeof module && !!module && !!module.exports) {
		// Preserve backwards compatibility
		module.exports = function() {
			return new Alertify();
		};
		var obj = new Alertify();
		for (var key in obj) {
			module.exports[key] = obj[key];
		}
	} else if (typeof define === "function" && define.amd) {
		define(function() {
			return new Alertify();
		});
	} else {
		window.alertify = new Alertify();
	}

}());